# ======================================================================
# Copyright CERFACS (June 2019)
# Contributor: Adrien Suau (adrien.suau@cerfacs.fr)
#
# This software is governed by the CeCILL-B license under French law and
# abiding  by the  rules of  distribution of free software. You can use,
# modify  and/or  redistribute  the  software  under  the  terms  of the
# CeCILL-B license as circulated by CEA, CNRS and INRIA at the following
# URL "http://www.cecill.info".
#
# As a counterpart to the access to  the source code and rights to copy,
# modify and  redistribute granted  by the  license, users  are provided
# only with a limited warranty and  the software's author, the holder of
# the economic rights,  and the  successive licensors  have only limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using, modifying and/or  developing or reproducing  the
# software by the user in light of its specific status of free software,
# that  may mean  that it  is complicated  to manipulate,  and that also
# therefore  means that  it is reserved for  developers and  experienced
# professionals having in-depth  computer knowledge. Users are therefore
# encouraged  to load and  test  the software's  suitability as  regards
# their  requirements  in  conditions  enabling  the  security  of their
# systems  and/or  data to be  ensured and,  more generally,  to use and
# operate it in the same conditions as regards security.
#
# The fact that you  are presently reading this  means that you have had
# knowledge of the CeCILL-B license and that you accept its terms.
# ======================================================================

"""Implementation of the main routine that should be used to simulate Hamiltonians.

This module contains the function that should be used to simulate any Hamiltonian matrix
with the product-formula approach.
"""

from qaths.simulation.pf.optimisation import lcr_optimise, no_optimisation
from qaths.simulation.pf.repetition_computation import compute_r
from qaths.simulation.pf.trotter import simulate_using_trotter


def simulate(
    oracles_generators,
    time: float,
    epsilon: float,
    max_spectral_norm_in_decomposition: float,
    trotter_suzuki_order: int = 1,
):
    r"""Simulate the Hamiltonian given as a decomposition in parameters.

    The simulated Hamiltonian should be decomposed in :math:`n` Hermitian matrices such
    as

    .. math::

       H = \sum_{k=0}^{n-1} H_k.

    :param oracles_generators: An iterable that yields :math:`n` functions that simulate
        the :math:`H_k` of the decomposition, i.e. that implement the operator
        :math:`e^{iH_kt}`. The yielded functions take as parameter :math:`t`, the
        desired evolution time.
    :param time: The time of evolution for the whole Hamiltonian :math:`H`.
    :param epsilon: The desired precision. The function assumes that the given
        `generators` are perfect. This means that if one of the generators only
        approximates the evolution :math:`e^{iH_kt}` instead of implementing it exactly,
        the circuit created by this function is not guaranteed to approximate the
        evolution :math:`e^{iHt}` with the desired precision.
    :param max_spectral_norm_in_decomposition: An upper bound of the spectral norm of
        the :math:`H_k`. Ideally, this parameter should be
        :math:`\max_k \vert\vert H_k \vert\vert` with :math:`||.||` the spectral norm.
    :param trotter_suzuki_order: The Trotter-Suzuki product-formula order used to
        approximate the evolution :math:`e^{iH\delta t}`, with :math:`\delta t` a small
        time.
    :return: a quantum circuit approximating the evolution :math:`e^{iHt}`.
    """
    if not isinstance(oracles_generators, list):
        oracles_generators = list(oracles_generators)
    repetitions = compute_r(
        time,
        epsilon,
        len(oracles_generators),
        max_spectral_norm_in_decomposition,
        trotter_suzuki_order,
    )
    gate = simulate_using_trotter(
        trotter_suzuki_order, time / repetitions, oracles_generators
    )
    return lcr_optimise(gate, repetitions, no_optimisation)
